package server

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/emicklei/proto"
	"github.com/spf13/cobra"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

// CmdServer the service command.
var CmdServer = &cobra.Command{
	Use:   "server",
	Short: "Generate the proto server implementations",
	Long:  "Generate the proto server implementations. Example: kratos proto server api/xxx.proto --target-dir=internal/service",
	Run:   run,
}
var targetDir string

func init() {
	CmdServer.Flags().StringVarP(&targetDir, "target-dir", "t", "internal/service", "generate target directory")
}

func run(_ *cobra.Command, args []string) {
	if len(args) == 0 {
		fmt.Fprintln(os.Stderr, "Please specify the proto file. Example: kratos proto server api/xxx.proto")
		return
	}
	reader, err := os.Open(args[0])
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()

	fmt.Println(args[0])
	parser := proto.NewParser(reader)
	definition, err := parser.Parse()
	if err != nil {
		log.Fatal(err)
	}

	var (
		pkg  string
		res  []*Service
		msgs []*proto.Message
	)
	proto.Walk(definition,
		proto.WithOption(func(o *proto.Option) {
			if o.Name == "go_package" {
				pkg = strings.Split(o.Constant.Source, ";")[0]
			}
		}),

		proto.WithService(func(s *proto.Service) {
			cs := &Service{
				Package: pkg,
				Service: serviceName(s.Name),
			}
			for _, e := range s.Elements {
				r, ok := e.(*proto.RPC)
				if !ok {
					continue
				}
				cs.Methods = append(cs.Methods, &Method{
					Service: serviceName(s.Name), Name: serviceName(r.Name), Request: parametersName(r.RequestType),
					Reply: parametersName(r.ReturnsType), Type: getMethodType(r.StreamsRequest, r.StreamsReturns),
				})
			}
			res = append(res, cs)
		}),

		proto.WithMessage(func(m *proto.Message) {
			msgs = append(msgs, m)
		}),
	)

	if err := NewObject(args[0], targetDir, res, msgs).Gen(); err != nil {
		fmt.Println(args[0], err.Error())
		return
	}
	//if _, err := os.Stat(targetDir); os.IsNotExist(err) {
	//	fmt.Printf("Target directory: %s does not exist\n", targetDir)
	//	return
	//}
	//for _, s := range res {
	//	to := filepath.Join(targetDir, strings.ToLower(s.Service)+".go")
	//	if _, err := os.Stat(to); !os.IsNotExist(err) {
	//		fileIsExists(s, to)
	//		continue
	//	}
	//	b, err := s.execute()
	//	if err != nil {
	//		log.Fatal(err)
	//	}
	//	if err := os.WriteFile(to, b, 0o644); err != nil {
	//		log.Fatal(err)
	//	}
	//}
}

func fileIsExists(s *Service, f string) error {
	body, err := os.ReadFile(f)
	if err != nil {
		return err
	}
	contents := strings.Split(string(body), "\n")

	isExistMothed := make(map[string]struct{})
	for _, content := range contents {
		if !strings.Contains(content, "func") || !strings.Contains(content, "*"+s.Service+"Service") {
			continue
		}

		if strings.Index(content, "*"+s.Service+"Service") > strings.Index(content, "context.Context") {
			continue
		}

		tmp := strings.Split(strings.Split(content, "*"+s.Service+"Service")[1], ")")[1]
		mothed := strings.TrimSpace(strings.Split(strings.TrimSpace(tmp), "(")[0])

		isExistMothed[mothed] = struct{}{}
	}

	var addContent string
	for _, method := range s.Methods {
		if method.Type != unaryType {
			continue
		}

		if _, ok := isExistMothed[method.Name]; ok {
			continue
		}

		addContent += fmt.Sprintf("\nfunc (s *%sService) %s(ctx context.Context, req *pb.%s) (*pb.%s, error){\n\treturn &pb.%s{}, nil\n}\n",
			s.Service, method.Name, method.Request, method.Reply, method.Reply)
	}

	os.Remove(f)

	content := strings.TrimSpace(string(body)) + addContent
	return os.WriteFile(f, []byte(content), 0o644)
}

func getMethodType(streamsRequest, streamsReturns bool) MethodType {
	if !streamsRequest && !streamsReturns {
		return unaryType
	} else if streamsRequest && streamsReturns {
		return twoWayStreamsType
	} else if streamsRequest {
		return requestStreamsType
	} else if streamsReturns {
		return returnsStreamsType
	}
	return unaryType
}

func parametersName(name string) string {
	return strings.ReplaceAll(name, ".", "_")
}

func serviceName(name string) string {
	return toUpperCamelCase(strings.Split(name, ".")[0])
}

func toUpperCamelCase(s string) string {
	s = strings.ReplaceAll(s, "_", " ")
	s = cases.Title(language.Und, cases.NoLower).String(s)
	return strings.ReplaceAll(s, " ", "")
}
